package Server;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Observable;
import java.util.logging.Logger;

import javax.swing.JOptionPane;

import GameModel.mGame;
import Message.MessageTypes;
import Message.XMLmessage;
import Player.mPlayer;

/**
 * 
 * @author Christopher Koller
 *
 */
public class mServer extends Observable {
	private ArrayList<mServerSessions> clientList;
	private ArrayList<mGame> openGames = new ArrayList<mGame>();
	private mNewConnectionListener listener;
	private static Logger log = Logger.getLogger("mServer");

	public static final int port = 4444;

	/**
	 * Startet den ServerSocket und beginnt mit dem Client-Managemet
	 */
	public void startServer() {
		try {
			listener = new mNewConnectionListener(this, port);
			listener.start();
			clientList = new ArrayList<mServerSessions>();
			System.out.println("Server started");
		} catch (IOException e) {
			JOptionPane.showMessageDialog(null, "Server is already started",
					"Error Message", 2);
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
			System.exit(0);
		}
	}

	/**
	 * Trennt alle Client-Verbindungen und beendet den Server
	 */
	public void stopServer() {
		listener.stopListening();

		// Important: Do not depend on structure of list,
		// as we will be changing it. Hence, "while not empty"
		this.openGames.clear();
		while (!clientList.isEmpty()) {
			mServerSessions client = clientList.get(0);
			client.close();
			removeClient(client);
		}
	}

	/**
	 * Fügt neue Clients der Verbindungsliste hinzu. Diese Methode
	 * benachrichtigt alle Observers
	 */
	public void addClient(mServerSessions newClient) {
		synchronized (clientList) {
			clientList.add(newClient);
			// setChanged();
			// notifyObservers(newClient);
		}
	}

	/**
	 * Informiert die Observers, dass eine User dem Server beigetreten ist
	 * 
	 * @param mServerSessions
	 *            ss
	 * @author Christopher Vogel
	 * 
	 */
	public void userJoin(mServerSessions ss) {
		setChanged();
		notifyObservers(ss);
	}

	/**
	 * Entfernt einen existierenden Client von der Liste, wenn die Verbindung
	 * geschlossen wurde.
	 *
	 * @param mServerSessions
	 *            oldClient
	 * @author Christopher Koller
	 */
	public void removeClient(mServerSessions oldClient) {
		synchronized (clientList) {
			for (Iterator<mServerSessions> i = clientList.iterator(); i
					.hasNext();) {
				mServerSessions client = i.next();
				if (client.equals(oldClient)) {
					i.remove();
					setChanged();
					notifyObservers(oldClient);
				}
			}
		}
		this.lobbyBroadcastPlayer();
	}

	public ArrayList<mServerSessions> getClientList() {
		return clientList;
	}

	public ArrayList<mGame> getOpenGames() {
		return openGames;
	}

	/**
	 * Fügt ein neues Spiel zu der Spielliste hinzu.
	 * 
	 * @param mGame
	 *            newGame
	 * @author Christopher Vogel
	 */
	public void addGame(mGame newGame) {
		openGames.add(newGame);
		setChanged();
		notifyObservers(newGame);
	}

	/**
	 * Entfernt das Spiel von der Spielliste, wenn es beendet wurde.
	 * 
	 * @param mGame
	 *            oldGame
	 * @author Christopher Vogel
	 */
	public void removeGame(mGame oldGame) {
		synchronized (clientList) {
			removeGameAtClient(oldGame);
			for (Iterator<mGame> i = openGames.iterator(); i.hasNext();) {
				mGame game = i.next();
				if (game.equals(oldGame))
					i.remove();
			}
			setChanged();
			notifyObservers(oldGame);
		}
	}

	/**
	 * Setze in allen ServerSessions-Instanzen, welche in diesem Spiel sind, das
	 * Game auf NULL
	 * 
	 * @param mGame
	 *            oldGame
	 * @author Christopher Vogel
	 */
	private void removeGameAtClient(mGame oldGame) {
		for (mServerSessions c : clientList) {
			if (oldGame.equals(c.getGame())) {
				c.setGame(null);
			}
		}

	}

	/**
	 * Sendet eine Nachricht an alle Spieler.
	 * 
	 * @param XMLmessage
	 *            msgIn
	 * @author Christopher Vogel
	 */
	public void broadcast(XMLmessage msgIn) {
		for (mServerSessions client : clientList) {
			try {
				msgIn.send(client.getSocket());
			} catch (Exception e) {
			}
		}
	}

	/**
	 * Sendet eine Nachricht an die Spieler welche im selben Spiel sind.
	 * 
	 * @param XMLmessage
	 *            msgIn
	 * @param mGame
	 *            mGame
	 * @author Christopher Vogel
	 */
	public void gameBroadcast(XMLmessage msgIn, mGame mGame) {
		int i = 0;
		try {
			for (mPlayer player : mGame.getPlayers()) {
				for (mServerSessions session : clientList) {
					try {
						if (player.equals(session.getPlayer())) {
							msgIn.send(session.getSocket());
							i++;
							break;
						}
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		System.out.println("Nachricht gesendet an :" + i);
	}

	public void gameBroadCastNewRound(mGame mGame) {
		XMLmessage msgGameRound = new XMLmessage(MessageTypes.NewGameRound,
				null);
		msgGameRound.getContent().setHandCardsList(mGame.getHandCards());
		msgGameRound.getContent().setHaggis(mGame.getHaggis());
		msgGameRound.getContent().setPlayer(mGame.getCurrentPlayer());
		this.gameBroadcast(msgGameRound, mGame);
	}

	/**
	 * Sendet eine Nachricht an die Spieler welche sich in der Lobby befinden.
	 *
	 * @param XMLmessage
	 *            msgIn
	 * @author Christopher Vogel
	 */
	public void lobbyBroadcast(XMLmessage msgIn) {
		for (mServerSessions client : clientList) {
			if (client.isLobby == true) {
				try {
					msgIn.send(client.getSocket());
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}

	/**
	 * Retourniert alle Spiele nach ihrem Status.
	 * 
	 * @param eGameState
	 *            state
	 * @return ArrayList<mGame>
	 * @author Christopher Vogel
	 */
	public ArrayList<mGame> getGamesByState(mGame.eGameState state) {
		ArrayList<mGame> games = new ArrayList<mGame>();
		if (openGames != null && openGames.size() != 0) {
			for (mGame game : openGames) {
				if (game != null && game.getGameState() == state)
					games.add(game);

			}
		}
		return games;
	}

	/**
	 * Sendet alle Spieler welche mit dem Server verbunden sind an die Spieler,
	 * welche sich in der Lobby befinden.
	 * 
	 * @author Christopher Vogel
	 */
	public void lobbyBroadcastPlayer() {
		XMLmessage msg = new XMLmessage(MessageTypes.PlayersOnline, null);
		ArrayList<mPlayer> players = new ArrayList<mPlayer>();
		for (mServerSessions client : clientList) {
			if (client.getPlayer() != null)
				players.add(client.getPlayer());
		}
		msg.getContent().setPlayerList(players);
		lobbyBroadcast(msg);
	}

	/**
	 * Sendet alle Spiele, welche auf weitere Spieler warten, an alle Spieler in
	 * der Lobby.
	 * 
	 * @author Christopher Vogel
	 */
	public void lobbyBroadcastGames() {
		XMLmessage msg = new XMLmessage(MessageTypes.OpenGamesShow, null);
		ArrayList<mGame> waitingForPlayers = getGamesByState(mGame.eGameState.waitingForPlayers);
		msg.getContent().setGamelist(waitingForPlayers);
		lobbyBroadcast(msg);
	}

	public void MergeSession(int SessionID, mServerSessions otherSession) {
		for (mServerSessions client : clientList) {
			if (client.SessionID == SessionID) {
				client.setSocket(otherSession.getSocket());
				client.sessionIsClosing = false;
				this.removeClient(otherSession);
				break;
			}
		}
	}

}
